#undef BLENDSTRETCH
#undef BLENDPIXELSTRETCH
#undef BLENDBORDERSTRETCH
#undef BLENDPIXELBICUBIC

#define BLENDSTRETCH MAKE_BLEND(BLEND_MODE, PIXEL_BITS, StretchBicubic, MMX)
#define BLENDPIXELSTRETCH MAKE_BLENDPIXELSTRETCH(BLEND_MODE, PIXEL_BITS, Bicubic, MMX)
#define BLENDBORDERSTRETCH MAKE_BLENDBORDERSTRETCH(BLEND_MODE, PIXEL_BITS, Bicubic, MMX)

#ifdef BLENDALPHA
    #define BLENDPIXELBICUBIC MAKE_BLENDPIXELSTRETCH(BLEND_MODE, PIXEL_BITS, PixelBicubicAlpha, MMX)
#else
    #define BLENDPIXELBICUBIC MAKE_BLENDPIXELSTRETCH(BLEND_MODE, PIXEL_BITS, PixelBicubic, MMX)
#endif

__forceinline void BLENDPIXELBICUBIC(const PBITMAPEX lpbBitmap, LPBYTE& pPixel, DWORD u8, DWORD v8, __m64& pixel)
{
    __m64 mc0 = _m_from_int(GETPIXEL(lpbBitmap, pPixel).argb);
    __m64 mc1 = _m_from_int(GETPIXEL(lpbBitmap, pPixel + PIXEL_BYTES).argb);
    __m64 mc2 = _m_from_int(GETPIXEL(lpbBitmap, pPixel + PIXEL_BYTES * 2).argb);
    __m64 mc3 = _m_from_int(GETPIXEL(lpbBitmap, pPixel + PIXEL_BYTES * 3).argb);
    __m64 mzero = _mm_setzero_si64();
    __m64 mu0 = _m_from_int(StretchTable[(1 << 8) + u8]);
    __m64 mu1 = _m_from_int(StretchTable[u8]);
    mc0 = _m_punpcklbw(mc0, mzero);
    mc1 = _m_punpcklbw(mc1, mzero);
    mu0 = _m_punpcklwd(mu0, mu0);
    mu1 = _m_punpcklwd(mu1, mu1);
    mc0 = _m_psllwi(mc0, 7);
    mc1 = _m_psllwi(mc1, 7);
    mc0 = _m_pmulhw(mc0, mu0);
    mc1 = _m_pmulhw(mc1, mu1);
    mc2 = _m_punpcklbw(mc2, mzero);
    mc3 = _m_punpcklbw(mc3, mzero);
    mu0 = _m_from_int(StretchTable[(1 << 8) - u8]);
    mu1 = _m_from_int(StretchTable[(2 << 8) - u8]);
    mu0 = _m_punpcklwd(mu0, mu0);
    mu1 = _m_punpcklwd(mu1, mu1);
    mc2 = _m_psllwi(mc2, 7);
    mc3 = _m_psllwi(mc3, 7);
    mc2 = _m_pmulhw(mc2, mu0);
    mc3 = _m_pmulhw(mc3, mu1);
    mc0 = _m_paddsw(mc0, mc1);
    mc2 = _m_paddsw(mc2, mc3);
    __m64 mv = _m_from_int(v8);
    mc0 = _m_paddsw(mc0, mc2);
    mv = _m_punpcklwd(mv, mv);
    mc0 = _m_pmulhw(mc0, mv);
    pixel = _m_paddsw(pixel, mc0);
    pPixel += lpbBitmap->bmWidthBytes;
}

#ifndef BLENDALPHA
__forceinline void BLENDPIXELSTRETCH(const PBITMAPEX lpbBitmap, const int x16, const int y16, LPXARGB result)
#else
__forceinline void BLENDPIXELSTRETCH(const PBITMAPEX lpbBitmap, const int x16, const int y16, LPXARGB result, __m64& alphaMask)
#endif
{
    DWORD u8 = (BYTE)(x16 >> 8);
    DWORD v8 = (BYTE)(y16 >> 8);
    LPBYTE pPixel = (LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * ((y16 >> 16) - 1) + ((x16 >> 16) - 1) * PIXEL_BYTES;
    __m64 pixel = _mm_setzero_si64();
    __m64 mzero = _mm_setzero_si64();
    DWORD av8 = StretchTable[(1 << 8) + v8];
    BLENDPIXELBICUBIC(lpbBitmap, pPixel, u8, av8, pixel);
    av8 = StretchTable[v8];
    BLENDPIXELBICUBIC(lpbBitmap, pPixel, u8, av8, pixel);
    av8 = StretchTable[(1 << 8) - v8];
    BLENDPIXELBICUBIC(lpbBitmap, pPixel, u8, av8, pixel);
    av8 = StretchTable[(2 << 8) - v8];
    BLENDPIXELBICUBIC(lpbBitmap, pPixel, u8, av8, pixel);
    pixel = _m_psrawi(pixel, 3);
    pixel = _m_packuswb(pixel, mzero);
#ifdef BLENDALPHA
    BLENDPIXEL(*(LPDWORD)result, _m_to_int(pixel), alphaMask);
#else
    BLENDPIXEL(*(LPDWORD)result, _m_to_int(pixel));
#endif
}

#ifndef BLENDALPHA
__forceinline void BLENDBORDERSTRETCH(const PBITMAPEX lpbBitmap, const int x16, const int y16, LPXARGB result)
#else
__forceinline void BLENDBORDERSTRETCH(const PBITMAPEX lpbBitmap, const int x16, const int y16, LPXARGB result, __m64& alphaMask)
#endif
{
    int x0Sub = (x16 >> 16) - 1;
    int y0Sub = (y16 >> 16) - 1;
    DWORD u16 = ((WORD)(x16)) + (1 << 16);
    DWORD v16 = ((WORD)(y16)) + (1 << 16);

    PIXEL pixel[16];
    for (int i = 0; i < 4; ++i)
    {
        int y = min(max(y0Sub + i, 0), lpbBitmap->bmHeight - 1);
        int x0 = min(max(x0Sub + 0, 0), lpbBitmap->bmWidth - 1);
        int x1 = min(max(x0Sub + 1, 0), lpbBitmap->bmWidth - 1);
        int x2 = min(max(x0Sub + 2, 0), lpbBitmap->bmWidth - 1);
        int x3 = min(max(x0Sub + 3, 0), lpbBitmap->bmWidth - 1);
        pixel[i*4+0] = *((PIXEL*)((LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * y) + x0);
        pixel[i*4+1] = *((PIXEL*)((LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * y) + x1);
        pixel[i*4+2] = *((PIXEL*)((LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * y) + x2);
        pixel[i*4+3] = *((PIXEL*)((LPBYTE)lpbBitmap->bmBits + lpbBitmap->bmWidthBytes * y) + x3);
    }

    BITMAPEX bmp;
    bmp.bmBits = &pixel[0];
    bmp.bmWidthBytes = 4 * PIXEL_BYTES;
#if PIXEL_BITS == 8
    bmp.bmClrUsed = lpbBitmap->bmClrUsed;
    bmp.bmColors = lpbBitmap->bmColors;
#endif
#ifndef BLENDALPHA
    BLENDPIXELSTRETCH(&bmp, u16, v16, result);
#else
    BLENDPIXELSTRETCH(&bmp, u16, v16, result, alphaMask);
#endif
}

#ifndef BLENDALPHA
BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc)
#else
BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc, BYTE alpha)
#endif
{
    DWORD xrIntFloat16 = (lpbSrc->bmWidth << 16) / lpbDst->bmWidth + 1;
    DWORD yrIntFloat16 = (lpbSrc->bmHeight << 16) / lpbDst->bmHeight + 1;

    const int csX = -(1 << 15) + ((int)xrIntFloat16 >> 1);
    const int csY = -(1 << 15) + ((int)yrIntFloat16 >> 1);

    int dstWidth = lpbDst->bmWidth;

    int borderY1 = ((1 << 16) - csY) / (int)yrIntFloat16 + 1;
    if (borderY1 >= lpbDst->bmHeight)
        borderY1 = lpbDst->bmHeight;
    int borderX1 = ((1 << 16) - csX) / (int)xrIntFloat16 + 1;
    if (borderX1 >= lpbDst->bmWidth)
        borderX1 = lpbDst->bmWidth;
    int borderY2 = (((lpbSrc->bmHeight - 3) << 16) - csY) / (int)yrIntFloat16 + 1;
    if (borderY2 < borderY1)
        borderY2 = borderY1;
    int borderX2 = (((lpbSrc->bmWidth - 3) << 16) - csX) / (int)xrIntFloat16 + 1; 
    if (borderX2 < borderX1)
        borderX2 = borderX1;

    LPXARGB pDstLine = (LPXARGB)lpbDst->bmBits;
    int srcy16 = csY;

#ifdef BLENDALPHA
    DWORD dwAlpha = (DWORD)alpha + 1;
    __m64 mAlpha = _m_from_int(dwAlpha);
    mAlpha = _m_punpcklwd(mAlpha, mAlpha);
    mAlpha = _m_punpckldq(mAlpha, mAlpha);
#endif

    for (int y = 0; y < borderY1; ++y)
    {
        int srcx16 = csX;
        for (int x = 0; x < dstWidth; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        srcy16 += yrIntFloat16;
        ((BYTE*&)pDstLine) += lpbDst->bmWidthBytes;
    }

    #pragma omp parallel for if ((borderY2 - borderY1) * (borderX2 - borderX1) > 0x40000) schedule(static) num_threads(omp_get_num_procs() - 1)
    for (int y = borderY1; y < borderY2; ++y)
    {
        LPXARGB pDstLine = (LPXARGB)((LPBYTE)lpbDst->bmBits + y * lpbDst->bmWidthBytes);
        int srcy16 = csY + y * yrIntFloat16;
        int srcx16 = csX;
        for (int x = 0; x < borderX1; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        for (int x = borderX1; x < borderX2; ++x)
        {
#ifndef BLENDALPHA
            BLENDPIXELSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDPIXELSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        for (int x = borderX2; x < dstWidth; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
    }
    pDstLine = (LPXARGB)((LPBYTE)lpbDst->bmBits + borderY2 * lpbDst->bmWidthBytes);
    srcy16 = csY + borderY2 * yrIntFloat16;
    for (int y = borderY2; y < lpbDst->bmHeight; ++y)
    {
        int srcx16 = csX;
        for (int x = 0; x < dstWidth; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        srcy16 += yrIntFloat16;
        ((BYTE*&)pDstLine) += lpbDst->bmWidthBytes;
    }
    _m_empty();
    return TRUE;
}

#ifndef BLENDALPHA
BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc, LPCSIZE lpszDst, LPCRECT lprcClipped)
#else
BOOL BLENDSTRETCH(PBITMAP lpbDst, PBITMAPEX lpbSrc, BYTE alpha, LPCSIZE lpszDst, LPCRECT lprcClipped)
#endif
{
    DWORD xrIntFloat16 = (lpbSrc->bmWidth << 16) / lpszDst->cx + 1;
    DWORD yrIntFloat16 = (lpbSrc->bmHeight << 16) / lpszDst->cy + 1;

    const int csX = -(1 << 15) + ((int)xrIntFloat16 >> 1);
    const int csY = -(1 << 15) + ((int)yrIntFloat16 >> 1);

    int borderY1 = ((1 << 16) - csY) / (int)yrIntFloat16 + 1;
    if (borderY1 >= lpszDst->cy)
        borderY1 = lpszDst->cy;
    int borderX1 = ((1 << 16) - csX) / (int)xrIntFloat16 + 1;
    if (borderX1 >= lpszDst->cx)
        borderX1 = lpszDst->cx;
    int borderY2 = (((lpbSrc->bmHeight - 3) << 16) - csY) / (int)yrIntFloat16 + 1;
    if (borderY2 < borderY1)
        borderY2 = borderY1;
    int borderX2 = (((lpbSrc->bmWidth - 3) << 16) - csX) / (int)xrIntFloat16 + 1; 
    if (borderX2 < borderX1)
        borderX2 = borderX1;

    DWORD dstByteWidth = lpbDst->bmWidthBytes;
    LPXARGB pDstLine = (LPXARGB)lpbDst->bmBits;
    int srcy16 = csY + lprcClipped->top * yrIntFloat16;
    int srcxOriginal16 = csX + lprcClipped->left * xrIntFloat16;

    if (borderX1 > lprcClipped->right) borderX1 = lprcClipped->right;
    if (borderX2 > lprcClipped->right) borderX2 = lprcClipped->right;
    if (borderY1 > lprcClipped->bottom) borderY1 = lprcClipped->bottom;
    if (borderY2 > lprcClipped->bottom) borderY2 = lprcClipped->bottom;
    if (borderX1 < lprcClipped->left) borderX1 = lprcClipped->left;
    if (borderX2 < lprcClipped->left) borderX2 = lprcClipped->left;
    if (borderY1 < lprcClipped->top) borderY1 = lprcClipped->top;
    if (borderY2 < lprcClipped->top) borderY2 = lprcClipped->top;

#ifdef BLENDALPHA
    DWORD dwAlpha = (DWORD)alpha + 1;
    __m64 mAlpha = _m_from_int(dwAlpha);
    mAlpha = _m_punpcklwd(mAlpha, mAlpha);
    mAlpha = _m_punpckldq(mAlpha, mAlpha);
#endif

    for (int y = lprcClipped->top; y < borderY1; ++y)
    {
        int srcx16 = srcxOriginal16;
        for (int x = lprcClipped->left; x < lprcClipped->right; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        srcy16 += yrIntFloat16;
        ((LPBYTE&)pDstLine) += dstByteWidth;
    }

    #pragma omp parallel for if ((borderY2 - borderY1) * (borderX2 - borderX1) > 0x40000) schedule(static) num_threads(omp_get_num_procs() - 1)
    for (int y = borderY1; y < borderY2; ++y)
    {
        LPXARGB pDstLine = (LPXARGB)((LPBYTE)lpbDst->bmBits + (y - lprcClipped->top) * lpbDst->bmWidthBytes);
        int srcy16 = csY + y * yrIntFloat16;
        int srcx16 = srcxOriginal16;
        for (int x = lprcClipped->left; x < borderX1; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        for (int x = borderX1; x < borderX2; ++x)
        {
#ifndef BLENDALPHA
            BLENDPIXELSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDPIXELSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        for (int x = borderX2; x < lprcClipped->right; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
    }
    pDstLine = (LPXARGB)((LPBYTE)lpbDst->bmBits + (borderY2 - lprcClipped->top) * lpbDst->bmWidthBytes);
    srcy16 = csY + borderY2 * yrIntFloat16;
    for (int y = borderY2; y < lprcClipped->bottom; ++y)
    {
        int srcx16 = srcxOriginal16;
        for (int x = lprcClipped->left; x < lprcClipped->right; ++x)
        {
#ifndef BLENDALPHA
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left]);
#else
            BLENDBORDERSTRETCH(lpbSrc, srcx16, srcy16, &pDstLine[x - lprcClipped->left], mAlpha);
#endif
            srcx16 += xrIntFloat16;
        }
        srcy16 += yrIntFloat16;
        ((LPBYTE&)pDstLine) += dstByteWidth;
    }
    _m_empty();
    return TRUE;
}